<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h3 data-lake-id="LJHvm" id="LJHvm"><span data-lake-id="u14f32432" id="u14f32432">背景</span></h3>
  <p data-lake-id="ua7e9495c" id="ua7e9495c"><br></p>
  <p data-lake-id="u75bf7df5" id="u75bf7df5"><span data-lake-id="ufa9390d5" id="ufa9390d5">负责了一个支付相关的功能，因为我们的支付渠道有很多，有的时候用户在选择某一个支付渠道支付（如微信）之后一直处理中，然后用户又换了一个其他的支付渠道（如支付宝）来支付。但是后面出现了两个支付都成功了的问题。但是我们的系统上没有针对这种情况做特殊处理，就导致用户多付了钱。</span></p>
  <p data-lake-id="uf73134a5" id="uf73134a5"><span data-lake-id="u3840def2" id="u3840def2">​</span><br></p>
  <h3 data-lake-id="TJFlD" id="TJFlD"><span data-lake-id="u55ff5f59" id="u55ff5f59">技术选型</span></h3>
  <p data-lake-id="u9825e582" id="u9825e582"><span data-lake-id="u85ebf05c" id="u85ebf05c">​</span><br></p>
  <p data-lake-id="uea2b6f44" id="uea2b6f44"><span data-lake-id="u96fd9a95" id="u96fd9a95">关于支付的并发问题，其实有几种方案，最简单的就是用户在某个支付渠道支付中的时候，不让他用其他的渠道再次支付，直到上一个渠道支付结果明确的成功或者失败。</span></p>
  <p data-lake-id="ua7811c97" id="ua7811c97"><span data-lake-id="uee8db9d7" id="uee8db9d7">​</span><br></p>
  <p data-lake-id="u718da196" id="u718da196"><span data-lake-id="u51c29728" id="u51c29728">这时候就需要给订单加锁，最开始我们就是这么做的。实现方式就是在每一个订单上，我们加了一个payStatus，payChannel和payStreamNo三个字段，当用户用某个渠道开始支付的时候：</span></p>
  <p data-lake-id="uafc55158" id="uafc55158"><span data-lake-id="ueb8b8202" id="ueb8b8202">​</span><br></p>
  <p data-lake-id="u41547e29" id="u41547e29"><span data-lake-id="u72e45f5d" id="u72e45f5d">payStatus = PAYING</span></p>
  <p data-lake-id="ud6335385" id="ud6335385"><span data-lake-id="ua7fc5c01" id="ua7fc5c01">payChannel = WECHAT</span></p>
  <p data-lake-id="ucba1573a" id="ucba1573a"><span data-lake-id="u5b1b1ace" id="u5b1b1ace">payStreanNo = NULL</span></p>
  <p data-lake-id="u3d099e69" id="u3d099e69"><span data-lake-id="u9510795c" id="u9510795c">​</span><br></p>
  <p data-lake-id="u1c4e0d6f" id="u1c4e0d6f"><span data-lake-id="u81464c58" id="u81464c58">这时候，如果微信支付的结果还没有返回的话，那么用户想要用支付宝再次针对这笔订单支付的时候会失败，因为payStatus = PAYING并且payChannel != ALIPAY ，所以会提示支付中。</span></p>
  <p data-lake-id="uea2b3dd3" id="uea2b3dd3"><span data-lake-id="ue2e2fd68" id="ue2e2fd68">​</span><br></p>
  <p data-lake-id="ucf6fc66e" id="ucf6fc66e"><span data-lake-id="u78e431d8" id="u78e431d8">一直到微信返回结果之后，我们再根据支付结果尝试继续处理，支付成功：</span></p>
  <p data-lake-id="uf3e59798" id="uf3e59798"><span data-lake-id="ubbfc2a61" id="ubbfc2a61">​</span><br></p>
  <p data-lake-id="u293e7a63" id="u293e7a63"><span data-lake-id="u3b729c17" id="u3b729c17">payStatus = PAID</span></p>
  <p data-lake-id="u04926f0f" id="u04926f0f"><span data-lake-id="uf652be59" id="uf652be59">payChannel = WECHAT</span></p>
  <p data-lake-id="u08d5e20a" id="u08d5e20a"><span data-lake-id="u039eda39" id="u039eda39">payStreanNo = 02312321321321423123</span></p>
  <p data-lake-id="u3cc19ae1" id="u3cc19ae1"><span data-lake-id="u6ded1b38" id="u6ded1b38">​</span><br></p>
  <p data-lake-id="u6878a14f" id="u6878a14f"><span data-lake-id="u600afff3" id="u600afff3">支付失败</span></p>
  <p data-lake-id="ue0ed88a4" id="ue0ed88a4"><span data-lake-id="ucf9fe567" id="ucf9fe567">​</span><br></p>
  <p data-lake-id="u2e6920b1" id="u2e6920b1"><span data-lake-id="ub6e1d01a" id="ub6e1d01a">payStatus = FAILED</span></p>
  <p data-lake-id="ub64facfe" id="ub64facfe"><span data-lake-id="u513e7605" id="u513e7605">payChannel = NULL</span></p>
  <p data-lake-id="u6ed3bc92" id="u6ed3bc92"><span data-lake-id="u2841def1" id="u2841def1">payStreanNo = NULL</span></p>
  <p data-lake-id="uf083d346" id="uf083d346"><span data-lake-id="u2b070a2d" id="u2b070a2d">​</span><br></p>
  <p data-lake-id="u74323995" id="u74323995"><span data-lake-id="ue4df409e" id="ue4df409e">但是，上面这个整体逻辑还存在一个问题，那就是当我们有并发请求，可能有两笔支付请求在检查payStatus的时候，发现都是INIT或者FAILED，那么这两个请求就都可以唤起支付渠道。</span></p>
  <p data-lake-id="u56741403" id="u56741403"><span data-lake-id="u0bf59198" id="u0bf59198">​</span><br></p>
  <p data-lake-id="ua0157fce" id="ua0157fce"><span data-lake-id="ubf56f32a" id="ubf56f32a">这个情况，在支付的时候比较少，因为支付都是用户自己操作的，问题不大，但是有一些场景可能会存在，比如说有那种花呗还款的场景，这时候就可能会有用户的主动还款，和系统的自动扣款。</span></p>
  <p data-lake-id="uda9b7c02" id="uda9b7c02"><span data-lake-id="u9d8cafec" id="u9d8cafec">​</span><br></p>
  <p data-lake-id="uaa0c67bc" id="uaa0c67bc"><span data-lake-id="uc1ee0de0" id="uc1ee0de0">那么这时候就可能会发生上述的并发情况，那么就需要想办法解决这个问题。</span></p>
  <p data-lake-id="u58da2f18" id="u58da2f18"><span data-lake-id="u352388c6" id="u352388c6">​</span><br></p>
  <h3 data-lake-id="ow9Qr" id="ow9Qr"><span data-lake-id="u32b961f3" id="u32b961f3">你做了什么</span></h3>
  <p data-lake-id="u17f36320" id="u17f36320"><br></p>
  <p data-lake-id="ubc8f2f89" id="ubc8f2f89"><span data-lake-id="udf369d94" id="udf369d94">这个地方其实主要就是并发情况下的一个幂等问题，所以为了解决并发的问题，我这里引入了一个分布式锁，在开始进行支付操作时，尝试添加分布式锁，加锁成功，再去判断payStatus。如果加锁失败，说明当前有并发请求，那么就失败掉。</span></p>
  <p data-lake-id="ud96b706e" id="ud96b706e"><span data-lake-id="u308cef30" id="u308cef30">​</span><br></p>
  <p data-lake-id="u69eaf77a" id="u69eaf77a"><span data-lake-id="u3a646f98" id="u3a646f98">伪代码如下：</span></p>
  <pre lang="java"><code>
//一锁：先加一个分布式锁
@DistributeLock(scene = "PAY", keyExpression = "#request.orderId", expire = 3000)
public PayResponse apply(PayRequest request) {
    PayResponse response = new PayResponse();
  	//二判：判断请求是否执行成功过
    OrderDTO orderDTO = orderService.queryOrder(request.getProduct(), request.getIdentifier());
    if (orderDTO != null &amp;&amp; orderDTO.getPayStatus() == PayStatus.PAYING) {
        response.setSuccess(false);
        response.setResponseCode("PAYING");
        return response;
    }

		//三更新：执行更新的业务逻辑
  	return orderService.applyPay(request);
}
</code></pre>
  <p data-lake-id="u40c26a90" id="u40c26a90"><br></p>
  <p data-lake-id="u0a19e073" id="u0a19e073"><br></p>
  <h3 data-lake-id="X5Av8" id="X5Av8"><span data-lake-id="ua0fdbdf0" id="ua0fdbdf0">学习资料</span></h3>
  <p data-lake-id="u85a2799f" id="u85a2799f"><span data-lake-id="u701e5310" id="u701e5310">​</span><br></p>
  <p data-lake-id="u82725096" id="u82725096"><br></p>
  <p data-lake-id="ud8024993" id="ud8024993"><br></p>
 </body>
</html>